;(function(window, document, navigator, undefined) {
"use strict";

/*
  utils.js
  ========
*/

/* Copyright  2017 Yahoo Inc.
 * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms.
*/

var utils = {
    URL: window.URL || window.webkitURL || window.mozURL || window.msURL,
    getUserMedia: function () {
        var getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;

        return getUserMedia ? getUserMedia.bind(navigator) : getUserMedia;
    }(),
    requestAnimFrame: window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame,
    requestTimeout: function requestTimeout(callback, delay) {
        callback = callback || utils.noop;
        delay = delay || 0;

        if (!utils.requestAnimFrame) {
            return setTimeout(callback, delay);
        }

        var start = new Date().getTime();
        var handle = new Object();
        var requestAnimFrame = utils.requestAnimFrame;

        var loop = function loop() {
            var current = new Date().getTime();
            var delta = current - start;

            delta >= delay ? callback.call() : handle.value = requestAnimFrame(loop);
        };

        handle.value = requestAnimFrame(loop);

        return handle;
    },
    Blob: window.Blob || window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder,
    btoa: function () {
        var btoa = window.btoa || function (input) {
            var output = '';
            var i = 0;
            var l = input.length;
            var key = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
            var chr1 = void 0;
            var chr2 = void 0;
            var chr3 = void 0;
            var enc1 = void 0;
            var enc2 = void 0;
            var enc3 = void 0;
            var enc4 = void 0;

            while (i < l) {
                chr1 = input.charCodeAt(i++);
                chr2 = input.charCodeAt(i++);
                chr3 = input.charCodeAt(i++);
                enc1 = chr1 >> 2;
                enc2 = (chr1 & 3) << 4 | chr2 >> 4;
                enc3 = (chr2 & 15) << 2 | chr3 >> 6;
                enc4 = chr3 & 63;

                if (isNaN(chr2)) {
                    enc3 = enc4 = 64;
                } else if (isNaN(chr3)) {
                    enc4 = 64;
                }

                output = output + key.charAt(enc1) + key.charAt(enc2) + key.charAt(enc3) + key.charAt(enc4);
            }

            return output;
        };

        return btoa ? btoa.bind(window) : utils.noop;
    }(),
    isObject: function isObject(obj) {
        return obj && Object.prototype.toString.call(obj) === '[object Object]';
    },
    isEmptyObject: function isEmptyObject(obj) {
        return utils.isObject(obj) && !Object.keys(obj).length;
    },
    isArray: function isArray(arr) {
        return arr && Array.isArray(arr);
    },
    isFunction: function isFunction(func) {
        return func && typeof func === 'function';
    },
    isElement: function isElement(elem) {
        return elem && elem.nodeType === 1;
    },
    isString: function isString(value) {
        return typeof value === 'string' || Object.prototype.toString.call(value) === '[object String]';
    },
    isSupported: {
        canvas: function canvas() {
            var el = document.createElement('canvas');

            return el && el.getContext && el.getContext('2d');
        },
        webworkers: function webworkers() {
            return window.Worker;
        },
        blob: function blob() {
            return utils.Blob;
        },
        Uint8Array: function Uint8Array() {
            return window.Uint8Array;
        },
        Uint32Array: function Uint32Array() {
            return window.Uint32Array;
        },
        videoCodecs: function () {
            var testEl = document.createElement('video');
            var supportObj = {
                'mp4': false,
                'h264': false,
                'ogv': false,
                'ogg': false,
                'webm': false
            };

            try {
                if (testEl && testEl.canPlayType) {
                    // Check for MPEG-4 support
                    supportObj.mp4 = testEl.canPlayType('video/mp4; codecs="mp4v.20.8"') !== '';

                    // Check for h264 support
                    supportObj.h264 = (testEl.canPlayType('video/mp4; codecs="avc1.42E01E"') || testEl.canPlayType('video/mp4; codecs="avc1.42E01E, mp4a.40.2"')) !== '';

                    // Check for Ogv support
                    supportObj.ogv = testEl.canPlayType('video/ogg; codecs="theora"') !== '';

                    // Check for Ogg support
                    supportObj.ogg = testEl.canPlayType('video/ogg; codecs="theora"') !== '';

                    // Check for Webm support
                    supportObj.webm = testEl.canPlayType('video/webm; codecs="vp8, vorbis"') !== -1;
                }
            } catch (e) {}

            return supportObj;
        }()
    },
    noop: function noop() {},
    each: function each(collection, callback) {
        var x = void 0;
        var len = void 0;

        if (utils.isArray(collection)) {
            x = -1;
            len = collection.length;

            while (++x < len) {
                if (callback(x, collection[x]) === false) {
                    break;
                }
            }
        } else if (utils.isObject(collection)) {
            for (x in collection) {
                if (collection.hasOwnProperty(x)) {
                    if (callback(x, collection[x]) === false) {
                        break;
                    }
                }
            }
        }
    },
    mergeOptions: function mergeOptions(defaultOptions, userOptions) {
        if (!utils.isObject(defaultOptions) || !utils.isObject(userOptions) || !Object.keys) {
            return;
        }

        var newObj = {};

        utils.each(defaultOptions, function (key, val) {
            newObj[key] = defaultOptions[key];
        });

        utils.each(userOptions, function (key, val) {
            var currentUserOption = userOptions[key];

            if (!utils.isObject(currentUserOption)) {
                newObj[key] = currentUserOption;
            } else {
                if (!defaultOptions[key]) {
                    newObj[key] = currentUserOption;
                } else {
                    newObj[key] = utils.mergeOptions(defaultOptions[key], currentUserOption);
                }
            }
        });

        return newObj;
    },
    setCSSAttr: function setCSSAttr(elem, attr, val) {
        if (!utils.isElement(elem)) {
            return;
        }

        if (utils.isString(attr) && utils.isString(val)) {
            elem.style[attr] = val;
        } else if (utils.isObject(attr)) {
            utils.each(attr, function (key, val) {
                elem.style[key] = val;
            });
        }
    },
    removeElement: function removeElement(node) {
        if (!utils.isElement(node)) {
            return;
        }
        if (node.parentNode) {
            node.parentNode.removeChild(node);
        }
    },
    createWebWorker: function createWebWorker(content) {
        if (!utils.isString(content)) {
            return {};
        }

        try {
            var blob = new utils.Blob([content], {
                'type': 'text/javascript'
            });
            var objectUrl = utils.URL.createObjectURL(blob);
            var worker = new Worker(objectUrl);

            return {
                'objectUrl': objectUrl,
                'worker': worker
            };
        } catch (e) {
            return '' + e;
        }
    },
    getExtension: function getExtension(src) {
        return src.substr(src.lastIndexOf('.') + 1, src.length);
    },
    getFontSize: function getFontSize() {
        var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};

        if (!document.body || options.resizeFont === false) {
            return options.fontSize;
        }

        var text = options.text;
        var containerWidth = options.gifWidth;
        var fontSize = parseInt(options.fontSize, 10);
        var minFontSize = parseInt(options.minFontSize, 10);
        var div = document.createElement('div');
        var span = document.createElement('span');

        div.setAttribute('width', containerWidth);
        div.appendChild(span);

        span.innerHTML = text;
        span.style.fontSize = fontSize + 'px';
        span.style.textIndent = '-9999px';
        span.style.visibility = 'hidden';

        document.body.appendChild(span);

        while (span.offsetWidth > containerWidth && fontSize >= minFontSize) {
            span.style.fontSize = --fontSize + 'px';
        }

        document.body.removeChild(span);

        return fontSize + 'px';
    },
    webWorkerError: false
};



var utils$2 = Object.freeze({
	default: utils
});

/*
  error.js
  ========
*/

/* Copyright  2017 Yahoo Inc.
 * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms.
*/

// Dependencies
var error = {
    validate: function validate(skipObj) {
        skipObj = utils.isObject(skipObj) ? skipObj : {};

        var errorObj = {};

        utils.each(error.validators, function (indece, currentValidator) {
            var errorCode = currentValidator.errorCode;

            if (!skipObj[errorCode] && !currentValidator.condition) {
                errorObj = currentValidator;
                errorObj.error = true;

                return false;
            }
        });

        delete errorObj.condition;

        return errorObj;
    },
    isValid: function isValid(skipObj) {
        var errorObj = error.validate(skipObj);
        var isValid = errorObj.error !== true ? true : false;

        return isValid;
    },
    validators: [{
        condition: utils.isFunction(utils.getUserMedia),
        errorCode: 'getUserMedia',
        errorMsg: 'The getUserMedia API is not supported in your browser'
    }, {
        condition: utils.isSupported.canvas(),
        errorCode: 'canvas',
        errorMsg: 'Canvas elements are not supported in your browser'
    }, {
        condition: utils.isSupported.webworkers(),
        errorCode: 'webworkers',
        errorMsg: 'The Web Workers API is not supported in your browser'
    }, {
        condition: utils.isFunction(utils.URL),
        errorCode: 'window.URL',
        errorMsg: 'The window.URL API is not supported in your browser'
    }, {
        condition: utils.isSupported.blob(),
        errorCode: 'window.Blob',
        errorMsg: 'The window.Blob File API is not supported in your browser'
    }, {
        condition: utils.isSupported.Uint8Array(),
        errorCode: 'window.Uint8Array',
        errorMsg: 'The window.Uint8Array function constructor is not supported in your browser'
    }, {
        condition: utils.isSupported.Uint32Array(),
        errorCode: 'window.Uint32Array',
        errorMsg: 'The window.Uint32Array function constructor is not supported in your browser'
    }],
    messages: {
        videoCodecs: {
            errorCode: 'videocodec',
            errorMsg: 'The video codec you are trying to use is not supported in your browser'
        }
    }
};



var error$2 = Object.freeze({
	default: error
});

/*
  defaultOptions.js
  =================
*/

/* Copyright  2017 Yahoo Inc.
 * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms.
*/

// Helpers
var noop = function noop() {};

var defaultOptions = {
    sampleInterval: 10,
    numWorkers: 2,
    filter: '',
    gifWidth: 200,
    gifHeight: 200,
    interval: 0.1,
    numFrames: 10,
    frameDuration: 1,
    keepCameraOn: false,
    images: [],
    video: null,
    webcamVideoElement: null,
    cameraStream: null,
    text: '',
    fontWeight: 'normal',
    fontSize: '16px',
    minFontSize: '10px',
    resizeFont: false,
    fontFamily: 'sans-serif',
    fontColor: '#ffffff',
    textAlign: 'center',
    textBaseline: 'bottom',
    textXCoordinate: null,
    textYCoordinate: null,
    progressCallback: noop,
    completeCallback: noop,
    saveRenderingContexts: false,
    savedRenderingContexts: [],
    crossOrigin: 'Anonymous'
};



var defaultOptions$2 = Object.freeze({
	default: defaultOptions
});

/*
  isSupported.js
  ==============
*/

/* Copyright  2017 Yahoo Inc.
 * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms.
*/

// Dependencies
function isSupported() {
  return error.isValid();
}

/*
  isWebCamGIFSupported.js
  =======================
*/

/* Copyright  2017 Yahoo Inc.
 * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms.
*/

function isWebCamGIFSupported() {
  return error.isValid();
}

/*
  isSupported.js
  ==============
*/

/* Copyright  2017 Yahoo Inc.
 * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms.
*/

// Dependencies
function isSupported$1() {
    var options = {
        getUserMedia: true
    };

    return error.isValid(options);
}

/*
  isExistingVideoGIFSupported.js
  ==============================
*/

/* Copyright  2017 Yahoo Inc.
 * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms.
*/

// Dependencies
function isExistingVideoGIFSupported(codecs) {
    var hasValidCodec = false;

    if (utils.isArray(codecs) && codecs.length) {
        utils.each(codecs, function (indece, currentCodec) {
            if (utils.isSupported.videoCodecs[currentCodec]) {
                hasValidCodec = true;
            }
        });

        if (!hasValidCodec) {
            return false;
        }
    } else if (utils.isString(codecs) && codecs.length) {
        if (!utils.isSupported.videoCodecs[codecs]) {
            return false;
        }
    }

    return error.isValid({
        'getUserMedia': true
    });
}

/*
  NeuQuant.js
  ===========
*/

/*
 * NeuQuant Neural-Net Quantization Algorithm
 * ------------------------------------------
 *
 * Copyright (c) 1994 Anthony Dekker
 *
 * NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994. See
 * "Kohonen neural networks for optimal colour quantization" in "Network:
 * Computation in Neural Systems" Vol. 5 (1994) pp 351-367. for a discussion of
 * the algorithm.
 *
 * Any party obtaining a copy of these files from the author, directly or
 * indirectly, is granted, free of charge, a full and unrestricted irrevocable,
 * world-wide, paid up, royalty-free, nonexclusive right and license to deal in
 * this software and documentation files (the "Software"), including without
 * limitation the rights to use, copy, modify, merge, publish, distribute,
 * sublicense, and/or sell copies of the Software, and to permit persons who
 * receive copies from any such party to do so, with the only requirement being
 * that this copyright notice remain intact.
 */

/*
 * This class handles Neural-Net quantization algorithm
 * @author Kevin Weiner (original Java version - kweiner@fmsware.com)
 * @author Thibault Imbert (AS3 version - bytearray.org)
 * @version 0.1 AS3 implementation
 * @version 0.2 JS->AS3 "translation" by antimatter15
 * @version 0.3 JS clean up + using modern JS idioms by sole - http://soledadpenades.com
 * Also implement fix in color conversion described at http://stackoverflow.com/questions/16371712/neuquant-js-javascript-color-quantization-hidden-bug-in-js-conversion
 */

function NeuQuant() {
  var netsize = 256; // number of colours used

  // four primes near 500 - assume no image has a length so large
  // that it is divisible by all four primes
  var prime1 = 499;
  var prime2 = 491;
  var prime3 = 487;
  var prime4 = 503;

  // minimum size for input image
  var minpicturebytes = 3 * prime4;

  // Network Definitions

  var maxnetpos = netsize - 1;
  var netbiasshift = 4; // bias for colour values
  var ncycles = 100; // no. of learning cycles

  // defs for freq and bias
  var intbiasshift = 16; // bias for fractions
  var intbias = 1 << intbiasshift;
  var gammashift = 10; // gamma = 1024
  var gamma = 1 << gammashift;
  var betashift = 10;
  var beta = intbias >> betashift; // beta = 1/1024
  var betagamma = intbias << gammashift - betashift;

  // defs for decreasing radius factor
  // For 256 colors, radius starts at 32.0 biased by 6 bits
  // and decreases by a factor of 1/30 each cycle
  var initrad = netsize >> 3;
  var radiusbiasshift = 6;
  var radiusbias = 1 << radiusbiasshift;
  var initradius = initrad * radiusbias;
  var radiusdec = 30;

  // defs for decreasing alpha factor
  // Alpha starts at 1.0 biased by 10 bits
  var alphabiasshift = 10;
  var initalpha = 1 << alphabiasshift;
  var alphadec;

  // radbias and alpharadbias used for radpower calculation
  var radbiasshift = 8;
  var radbias = 1 << radbiasshift;
  var alpharadbshift = alphabiasshift + radbiasshift;
  var alpharadbias = 1 << alpharadbshift;

  // Input image
  var thepicture;
  // Height * Width * 3
  var lengthcount;
  // Sampling factor 1..30
  var samplefac;

  // The network itself
  var network;
  var netindex = [];

  // for network lookup - really 256
  var bias = [];

  // bias and freq arrays for learning
  var freq = [];
  var radpower = [];

  function NeuQuantConstructor(thepic, len, sample) {

    var i;
    var p;

    thepicture = thepic;
    lengthcount = len;
    samplefac = sample;

    network = new Array(netsize);

    for (i = 0; i < netsize; i++) {
      network[i] = new Array(4);
      p = network[i];
      p[0] = p[1] = p[2] = (i << netbiasshift + 8) / netsize | 0;
      freq[i] = intbias / netsize | 0; // 1 / netsize
      bias[i] = 0;
    }
  }

  function colorMap() {
    var map = [];
    var index = new Array(netsize);
    for (var i = 0; i < netsize; i++) {
      index[network[i][3]] = i;
    }var k = 0;
    for (var l = 0; l < netsize; l++) {
      var j = index[l];
      map[k++] = network[j][0];
      map[k++] = network[j][1];
      map[k++] = network[j][2];
    }
    return map;
  }

  // Insertion sort of network and building of netindex[0..255]
  // (to do after unbias)
  function inxbuild() {
    var i;
    var j;
    var smallpos;
    var smallval;
    var p;
    var q;
    var previouscol;
    var startpos;

    previouscol = 0;
    startpos = 0;

    for (i = 0; i < netsize; i++) {

      p = network[i];
      smallpos = i;
      smallval = p[1]; // index on g
      // find smallest in i..netsize-1
      for (j = i + 1; j < netsize; j++) {

        q = network[j];

        if (q[1] < smallval) {
          // index on g
          smallpos = j;
          smallval = q[1]; // index on g
        }
      }

      q = network[smallpos];

      // swap p (i) and q (smallpos) entries
      if (i != smallpos) {
        j = q[0];
        q[0] = p[0];
        p[0] = j;
        j = q[1];
        q[1] = p[1];
        p[1] = j;
        j = q[2];
        q[2] = p[2];
        p[2] = j;
        j = q[3];
        q[3] = p[3];
        p[3] = j;
      }

      // smallval entry is now in position i
      if (smallval != previouscol) {

        netindex[previouscol] = startpos + i >> 1;

        for (j = previouscol + 1; j < smallval; j++) {
          netindex[j] = i;
        }

        previouscol = smallval;
        startpos = i;
      }
    }

    netindex[previouscol] = startpos + maxnetpos >> 1;
    for (j = previouscol + 1; j < 256; j++) {
      netindex[j] = maxnetpos; // really 256
    }
  }

  // Main Learning Loop

  function learn() {
    var i;
    var j;
    var b;
    var g;
    var r;
    var radius;
    var rad;
    var alpha;
    var step;
    var delta;
    var samplepixels;
    var p;
    var pix;
    var lim;

    if (lengthcount < minpicturebytes) {
      samplefac = 1;
    }

    alphadec = 30 + (samplefac - 1) / 3;
    p = thepicture;
    pix = 0;
    lim = lengthcount;
    samplepixels = lengthcount / (3 * samplefac);
    delta = samplepixels / ncycles | 0;
    alpha = initalpha;
    radius = initradius;

    rad = radius >> radiusbiasshift;
    if (rad <= 1) {
      rad = 0;
    }

    for (i = 0; i < rad; i++) {
      radpower[i] = alpha * ((rad * rad - i * i) * radbias / (rad * rad));
    }

    if (lengthcount < minpicturebytes) {
      step = 3;
    } else if (lengthcount % prime1 !== 0) {
      step = 3 * prime1;
    } else {

      if (lengthcount % prime2 !== 0) {
        step = 3 * prime2;
      } else {
        if (lengthcount % prime3 !== 0) {
          step = 3 * prime3;
        } else {
          step = 3 * prime4;
        }
      }
    }

    i = 0;

    while (i < samplepixels) {

      b = (p[pix + 0] & 0xff) << netbiasshift;
      g = (p[pix + 1] & 0xff) << netbiasshift;
      r = (p[pix + 2] & 0xff) << netbiasshift;
      j = contest(b, g, r);

      altersingle(alpha, j, b, g, r);

      if (rad !== 0) {
        // Alter neighbours
        alterneigh(rad, j, b, g, r);
      }

      pix += step;

      if (pix >= lim) {
        pix -= lengthcount;
      }

      i++;

      if (delta === 0) {
        delta = 1;
      }

      if (i % delta === 0) {
        alpha -= alpha / alphadec;
        radius -= radius / radiusdec;
        rad = radius >> radiusbiasshift;

        if (rad <= 1) {
          rad = 0;
        }

        for (j = 0; j < rad; j++) {
          radpower[j] = alpha * ((rad * rad - j * j) * radbias / (rad * rad));
        }
      }
    }
  }

  // Search for BGR values 0..255 (after net is unbiased) and return colour index
  function map(b, g, r) {
    var i;
    var j;
    var dist;
    var a;
    var bestd;
    var p;
    var best;

    // Biggest possible distance is 256 * 3
    bestd = 1000;
    best = -1;
    i = netindex[g]; // index on g
    j = i - 1; // start at netindex[g] and work outwards

    while (i < netsize || j >= 0) {

      if (i < netsize) {

        p = network[i];

        dist = p[1] - g; // inx key

        if (dist >= bestd) {
          i = netsize; // stop iter
        } else {

          i++;

          if (dist < 0) {
            dist = -dist;
          }

          a = p[0] - b;

          if (a < 0) {
            a = -a;
          }

          dist += a;

          if (dist < bestd) {
            a = p[2] - r;

            if (a < 0) {
              a = -a;
            }

            dist += a;

            if (dist < bestd) {
              bestd = dist;
              best = p[3];
            }
          }
        }
      }

      if (j >= 0) {

        p = network[j];

        dist = g - p[1]; // inx key - reverse dif

        if (dist >= bestd) {
          j = -1; // stop iter
        } else {

          j--;
          if (dist < 0) {
            dist = -dist;
          }
          a = p[0] - b;
          if (a < 0) {
            a = -a;
          }
          dist += a;

          if (dist < bestd) {
            a = p[2] - r;
            if (a < 0) {
              a = -a;
            }
            dist += a;
            if (dist < bestd) {
              bestd = dist;
              best = p[3];
            }
          }
        }
      }
    }

    return best;
  }

  function process() {
    learn();
    unbiasnet();
    inxbuild();
    return colorMap();
  }

  // Unbias network to give byte values 0..255 and record position i
  // to prepare for sort
  function unbiasnet() {
    var i;
    var j;

    for (i = 0; i < netsize; i++) {
      network[i][0] >>= netbiasshift;
      network[i][1] >>= netbiasshift;
      network[i][2] >>= netbiasshift;
      network[i][3] = i; // record colour no
    }
  }

  // Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2))
  // in radpower[|i-j|]
  function alterneigh(rad, i, b, g, r) {

    var j;
    var k;
    var lo;
    var hi;
    var a;
    var m;

    var p;

    lo = i - rad;
    if (lo < -1) {
      lo = -1;
    }

    hi = i + rad;

    if (hi > netsize) {
      hi = netsize;
    }

    j = i + 1;
    k = i - 1;
    m = 1;

    while (j < hi || k > lo) {

      a = radpower[m++];

      if (j < hi) {

        p = network[j++];

        try {

          p[0] -= a * (p[0] - b) / alpharadbias | 0;
          p[1] -= a * (p[1] - g) / alpharadbias | 0;
          p[2] -= a * (p[2] - r) / alpharadbias | 0;
        } catch (e) {}
      }

      if (k > lo) {

        p = network[k--];

        try {

          p[0] -= a * (p[0] - b) / alpharadbias | 0;
          p[1] -= a * (p[1] - g) / alpharadbias | 0;
          p[2] -= a * (p[2] - r) / alpharadbias | 0;
        } catch (e) {}
      }
    }
  }

  // Move neuron i towards biased (b,g,r) by factor alpha
  function altersingle(alpha, i, b, g, r) {

    // alter hit neuron
    var n = network[i];
    var alphaMult = alpha / initalpha;
    n[0] -= alphaMult * (n[0] - b) | 0;
    n[1] -= alphaMult * (n[1] - g) | 0;
    n[2] -= alphaMult * (n[2] - r) | 0;
  }

  // Search for biased BGR values
  function contest(b, g, r) {

    // finds closest neuron (min dist) and updates freq
    // finds best neuron (min dist-bias) and returns position
    // for frequently chosen neurons, freq[i] is high and bias[i] is negative
    // bias[i] = gamma*((1/netsize)-freq[i])

    var i;
    var dist;
    var a;
    var biasdist;
    var betafreq;
    var bestpos;
    var bestbiaspos;
    var bestd;
    var bestbiasd;
    var n;

    bestd = ~(1 << 31);
    bestbiasd = bestd;
    bestpos = -1;
    bestbiaspos = bestpos;

    for (i = 0; i < netsize; i++) {

      n = network[i];
      dist = n[0] - b;

      if (dist < 0) {
        dist = -dist;
      }

      a = n[1] - g;

      if (a < 0) {
        a = -a;
      }

      dist += a;

      a = n[2] - r;

      if (a < 0) {
        a = -a;
      }

      dist += a;

      if (dist < bestd) {
        bestd = dist;
        bestpos = i;
      }

      biasdist = dist - (bias[i] >> intbiasshift - netbiasshift);

      if (biasdist < bestbiasd) {
        bestbiasd = biasdist;
        bestbiaspos = i;
      }

      betafreq = freq[i] >> betashift;
      freq[i] -= betafreq;
      bias[i] += betafreq << gammashift;
    }

    freq[bestpos] += beta;
    bias[bestpos] -= betagamma;
    return bestbiaspos;
  }

  NeuQuantConstructor.apply(this, arguments);

  var exports = {};
  exports.map = map;
  exports.process = process;

  return exports;
}

/*
  processFrameWorker.js
  =====================
*/

/* Copyright  2017 Yahoo Inc.
 * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms.
*/

function workerCode() {
    var self = this;

    try {
        self.onmessage = function (ev) {
            var data = ev.data || {};
            var response;

            if (data.gifshot) {
                response = workerMethods.run(data);
                postMessage(response);
            }
        };
    } catch (e) {}

    var workerMethods = {
        dataToRGB: function dataToRGB(data, width, height) {
            var length = width * height * 4;
            var i = 0;
            var rgb = [];

            while (i < length) {
                rgb.push(data[i++]);
                rgb.push(data[i++]);
                rgb.push(data[i++]);
                i++; // for the alpha channel which we don't care about
            }

            return rgb;
        },
        componentizedPaletteToArray: function componentizedPaletteToArray(paletteRGB) {
            paletteRGB = paletteRGB || [];

            var paletteArray = [];

            for (var i = 0; i < paletteRGB.length; i += 3) {
                var r = paletteRGB[i];
                var g = paletteRGB[i + 1];
                var b = paletteRGB[i + 2];

                paletteArray.push(r << 16 | g << 8 | b);
            }

            return paletteArray;
        },
        // This is the "traditional" Animated_GIF style of going from RGBA to indexed color frames
        'processFrameWithQuantizer': function processFrameWithQuantizer(imageData, width, height, sampleInterval) {
            var rgbComponents = this.dataToRGB(imageData, width, height);
            var nq = new NeuQuant(rgbComponents, rgbComponents.length, sampleInterval);
            var paletteRGB = nq.process();
            var paletteArray = new Uint32Array(this.componentizedPaletteToArray(paletteRGB));
            var numberPixels = width * height;
            var indexedPixels = new Uint8Array(numberPixels);
            var k = 0;

            for (var i = 0; i < numberPixels; i++) {
                var r = rgbComponents[k++];
                var g = rgbComponents[k++];
                var b = rgbComponents[k++];

                indexedPixels[i] = nq.map(r, g, b);
            }

            return {
                pixels: indexedPixels,
                palette: paletteArray
            };
        },
        'run': function run(frame) {
            frame = frame || {};

            var _frame = frame,
                height = _frame.height,
                palette = _frame.palette,
                sampleInterval = _frame.sampleInterval,
                width = _frame.width;

            var imageData = frame.data;

            return this.processFrameWithQuantizer(imageData, width, height, sampleInterval);
        }
    };

    return workerMethods;
}

/*
  gifWriter.js
  ============
*/

// (c) Dean McNamee <dean@gmail.com>, 2013.
//
// https://github.com/deanm/omggif
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
// omggif is a JavaScript implementation of a GIF 89a encoder and decoder,
// including animation and compression.  It does not rely on any specific
// underlying system, so should run in the browser, Node, or Plask.

function gifWriter(buf, width, height, gopts) {
  var p = 0;

  gopts = gopts === undefined ? {} : gopts;
  var loop_count = gopts.loop === undefined ? null : gopts.loop;
  var global_palette = gopts.palette === undefined ? null : gopts.palette;

  if (width <= 0 || height <= 0 || width > 65535 || height > 65535) throw "Width/Height invalid.";

  function check_palette_and_num_colors(palette) {
    var num_colors = palette.length;

    if (num_colors < 2 || num_colors > 256 || num_colors & num_colors - 1) throw "Invalid code/color length, must be power of 2 and 2 .. 256.";
    return num_colors;
  }

  // - Header.
  buf[p++] = 0x47;
  buf[p++] = 0x49;
  buf[p++] = 0x46; // GIF
  buf[p++] = 0x38;
  buf[p++] = 0x39;
  buf[p++] = 0x61; // 89a

  // Handling of Global Color Table (palette) and background index.
  var gp_num_colors_pow2 = 0;
  var background = 0;

  // - Logical Screen Descriptor.
  // NOTE(deanm): w/h apparently ignored by implementations, but set anyway.
  buf[p++] = width & 0xff;
  buf[p++] = width >> 8 & 0xff;
  buf[p++] = height & 0xff;
  buf[p++] = height >> 8 & 0xff;
  // NOTE: Indicates 0-bpp original color resolution (unused?).
  buf[p++] = (global_palette !== null ? 0x80 : 0) | // Global Color Table Flag.
  gp_num_colors_pow2; // NOTE: No sort flag (unused?).
  buf[p++] = background; // Background Color Index.
  buf[p++] = 0; // Pixel aspect ratio (unused?).

  if (loop_count !== null) {
    // Netscape block for looping.
    if (loop_count < 0 || loop_count > 65535) throw "Loop count invalid.";

    // Extension code, label, and length.
    buf[p++] = 0x21;
    buf[p++] = 0xff;
    buf[p++] = 0x0b;
    // NETSCAPE2.0
    buf[p++] = 0x4e;
    buf[p++] = 0x45;
    buf[p++] = 0x54;
    buf[p++] = 0x53;
    buf[p++] = 0x43;
    buf[p++] = 0x41;
    buf[p++] = 0x50;
    buf[p++] = 0x45;
    buf[p++] = 0x32;
    buf[p++] = 0x2e;
    buf[p++] = 0x30;
    // Sub-block
    buf[p++] = 0x03;
    buf[p++] = 0x01;
    buf[p++] = loop_count & 0xff;
    buf[p++] = loop_count >> 8 & 0xff;
    buf[p++] = 0x00; // Terminator.
  }

  var ended = false;

  this.addFrame = function (x, y, w, h, indexed_pixels, opts) {
    if (ended === true) {
      --p;
      ended = false;
    } // Un-end.

    opts = opts === undefined ? {} : opts;

    // TODO(deanm): Bounds check x, y.  Do they need to be within the virtual
    // canvas width/height, I imagine?
    if (x < 0 || y < 0 || x > 65535 || y > 65535) throw "x/y invalid.";

    if (w <= 0 || h <= 0 || w > 65535 || h > 65535) throw "Width/Height invalid.";

    if (indexed_pixels.length < w * h) throw "Not enough pixels for the frame size.";

    var using_local_palette = true;
    var palette = opts.palette;
    if (palette === undefined || palette === null) {
      using_local_palette = false;
      palette = global_palette;
    }

    if (palette === undefined || palette === null) throw "Must supply either a local or global palette.";

    var num_colors = check_palette_and_num_colors(palette);

    // Compute the min_code_size (power of 2), destroying num_colors.
    var min_code_size = 0;
    while (num_colors >>= 1) {
      ++min_code_size;
    }num_colors = 1 << min_code_size; // Now we can easily get it back.

    var delay = opts.delay === undefined ? 0 : opts.delay;

    // From the spec:
    //     0 -   No disposal specified. The decoder is
    //           not required to take any action.
    //     1 -   Do not dispose. The graphic is to be left
    //           in place.
    //     2 -   Restore to background color. The area used by the
    //           graphic must be restored to the background color.
    //     3 -   Restore to previous. The decoder is required to
    //           restore the area overwritten by the graphic with
    //           what was there prior to rendering the graphic.
    //  4-7 -    To be defined.
    // NOTE(deanm): Dispose background doesn't really work, apparently most
    // browsers ignore the background palette index and clear to transparency.
    var disposal = opts.disposal === undefined ? 0 : opts.disposal;
    if (disposal < 0 || disposal > 3) // 4-7 is reserved.
      throw "Disposal out of range.";

    var use_transparency = false;
    var transparent_index = 0;
    if (opts.transparent !== undefined && opts.transparent !== null) {
      use_transparency = true;
      transparent_index = opts.transparent;
      if (transparent_index < 0 || transparent_index >= num_colors) throw "Transparent color index.";
    }

    if (disposal !== 0 || use_transparency || delay !== 0) {
      // - Graphics Control Extension
      buf[p++] = 0x21;
      buf[p++] = 0xf9; // Extension / Label.
      buf[p++] = 4; // Byte size.

      buf[p++] = disposal << 2 | (use_transparency === true ? 1 : 0);
      buf[p++] = delay & 0xff;
      buf[p++] = delay >> 8 & 0xff;
      buf[p++] = transparent_index; // Transparent color index.
      buf[p++] = 0; // Block Terminator.
    }

    // - Image Descriptor
    buf[p++] = 0x2c; // Image Seperator.
    buf[p++] = x & 0xff;
    buf[p++] = x >> 8 & 0xff; // Left.
    buf[p++] = y & 0xff;
    buf[p++] = y >> 8 & 0xff; // Top.
    buf[p++] = w & 0xff;
    buf[p++] = w >> 8 & 0xff;
    buf[p++] = h & 0xff;
    buf[p++] = h >> 8 & 0xff;
    // NOTE: No sort flag (unused?).
    // TODO(deanm): Support interlace.
    buf[p++] = using_local_palette === true ? 0x80 | min_code_size - 1 : 0;

    // - Local Color Table
    if (using_local_palette === true) {
      for (var i = 0, il = palette.length; i < il; ++i) {
        var rgb = palette[i];
        buf[p++] = rgb >> 16 & 0xff;
        buf[p++] = rgb >> 8 & 0xff;
        buf[p++] = rgb & 0xff;
      }
    }

    p = GifWriterOutputLZWCodeStream(buf, p, min_code_size < 2 ? 2 : min_code_size, indexed_pixels);
  };

  this.end = function () {
    if (ended === false) {
      buf[p++] = 0x3b; // Trailer.
      ended = true;
    }
    return p;
  };

  // Main compression routine, palette indexes -> LZW code stream.
  // |index_stream| must have at least one entry.
  function GifWriterOutputLZWCodeStream(buf, p, min_code_size, index_stream) {
    buf[p++] = min_code_size;
    var cur_subblock = p++; // Pointing at the length field.

    var clear_code = 1 << min_code_size;
    var code_mask = clear_code - 1;
    var eoi_code = clear_code + 1;
    var next_code = eoi_code + 1;

    var cur_code_size = min_code_size + 1; // Number of bits per code.
    var cur_shift = 0;
    // We have at most 12-bit codes, so we should have to hold a max of 19
    // bits here (and then we would write out).
    var cur = 0;

    function emit_bytes_to_buffer(bit_block_size) {
      while (cur_shift >= bit_block_size) {
        buf[p++] = cur & 0xff;
        cur >>= 8;
        cur_shift -= 8;
        if (p === cur_subblock + 256) {
          // Finished a subblock.
          buf[cur_subblock] = 255;
          cur_subblock = p++;
        }
      }
    }

    function emit_code(c) {
      cur |= c << cur_shift;
      cur_shift += cur_code_size;
      emit_bytes_to_buffer(8);
    }

    // I am not an expert on the topic, and I don't want to write a thesis.
    // However, it is good to outline here the basic algorithm and the few data
    // structures and optimizations here that make this implementation fast.
    // The basic idea behind LZW is to build a table of previously seen runs
    // addressed by a short id (herein called output code).  All data is
    // referenced by a code, which represents one or more values from the
    // original input stream.  All input bytes can be referenced as the same
    // value as an output code.  So if you didn't want any compression, you
    // could more or less just output the original bytes as codes (there are
    // some details to this, but it is the idea).  In order to achieve
    // compression, values greater then the input range (codes can be up to
    // 12-bit while input only 8-bit) represent a sequence of previously seen
    // inputs.  The decompressor is able to build the same mapping while
    // decoding, so there is always a shared common knowledge between the
    // encoding and decoder, which is also important for "timing" aspects like
    // how to handle variable bit width code encoding.
    //
    // One obvious but very important consequence of the table system is there
    // is always a unique id (at most 12-bits) to map the runs.  'A' might be
    // 4, then 'AA' might be 10, 'AAA' 11, 'AAAA' 12, etc.  This relationship
    // can be used for an effecient lookup strategy for the code mapping.  We
    // need to know if a run has been seen before, and be able to map that run
    // to the output code.  Since we start with known unique ids (input bytes),
    // and then from those build more unique ids (table entries), we can
    // continue this chain (almost like a linked list) to always have small
    // integer values that represent the current byte chains in the encoder.
    // This means instead of tracking the input bytes (AAAABCD) to know our
    // current state, we can track the table entry for AAAABC (it is guaranteed
    // to exist by the nature of the algorithm) and the next character D.
    // Therefor the tuple of (table_entry, byte) is guaranteed to also be
    // unique.  This allows us to create a simple lookup key for mapping input
    // sequences to codes (table indices) without having to store or search
    // any of the code sequences.  So if 'AAAA' has a table entry of 12, the
    // tuple of ('AAAA', K) for any input byte K will be unique, and can be our
    // key.  This leads to a integer value at most 20-bits, which can always
    // fit in an SMI value and be used as a fast sparse array / object key.

    // Output code for the current contents of the index buffer.
    var ib_code = index_stream[0] & code_mask; // Load first input index.
    var code_table = {}; // Key'd on our 20-bit "tuple".

    emit_code(clear_code); // Spec says first code should be a clear code.

    // First index already loaded, process the rest of the stream.
    for (var i = 1, il = index_stream.length; i < il; ++i) {
      var k = index_stream[i] & code_mask;
      var cur_key = ib_code << 8 | k; // (prev, k) unique tuple.
      var cur_code = code_table[cur_key]; // buffer + k.

      // Check if we have to create a new code table entry.
      if (cur_code === undefined) {
        // We don't have buffer + k.
        // Emit index buffer (without k).
        // This is an inline version of emit_code, because this is the core
        // writing routine of the compressor (and V8 cannot inline emit_code
        // because it is a closure here in a different context).  Additionally
        // we can call emit_byte_to_buffer less often, because we can have
        // 30-bits (from our 31-bit signed SMI), and we know our codes will only
        // be 12-bits, so can safely have 18-bits there without overflow.
        // emit_code(ib_code);
        cur |= ib_code << cur_shift;
        cur_shift += cur_code_size;
        while (cur_shift >= 8) {
          buf[p++] = cur & 0xff;
          cur >>= 8;
          cur_shift -= 8;
          if (p === cur_subblock + 256) {
            // Finished a subblock.
            buf[cur_subblock] = 255;
            cur_subblock = p++;
          }
        }

        if (next_code === 4096) {
          // Table full, need a clear.
          emit_code(clear_code);
          next_code = eoi_code + 1;
          cur_code_size = min_code_size + 1;
          code_table = {};
        } else {
          // Table not full, insert a new entry.
          // Increase our variable bit code sizes if necessary.  This is a bit
          // tricky as it is based on "timing" between the encoding and
          // decoder.  From the encoders perspective this should happen after
          // we've already emitted the index buffer and are about to create the
          // first table entry that would overflow our current code bit size.
          if (next_code >= 1 << cur_code_size) ++cur_code_size;
          code_table[cur_key] = next_code++; // Insert into code table.
        }

        ib_code = k; // Index buffer to single input k.
      } else {
        ib_code = cur_code; // Index buffer to sequence in code table.
      }
    }

    emit_code(ib_code); // There will still be something in the index buffer.
    emit_code(eoi_code); // End Of Information.

    // Flush / finalize the sub-blocks stream to the buffer.
    emit_bytes_to_buffer(1);

    // Finish the sub-blocks, writing out any unfinished lengths and
    // terminating with a sub-block of length 0.  If we have already started
    // but not yet used a sub-block it can just become the terminator.
    if (cur_subblock + 1 === p) {
      // Started but unused.
      buf[cur_subblock] = 0;
    } else {
      // Started and used, write length and additional terminator block.
      buf[cur_subblock] = p - cur_subblock - 1;
      buf[p++] = 0;
    }
    return p;
  }
}

/*
  animatedGIF.js
  ==============
*/

/* Copyright  2017 Yahoo Inc.
 * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms.
*/

// Dependencies
// Helpers
var noop$2 = function noop() {};

var AnimatedGIF = function AnimatedGIF(options) {
    this.canvas = null;
    this.ctx = null;
    this.repeat = 0;
    this.frames = [];
    this.numRenderedFrames = 0;
    this.onRenderCompleteCallback = noop$2;
    this.onRenderProgressCallback = noop$2;
    this.workers = [];
    this.availableWorkers = [];
    this.generatingGIF = false;
    this.options = options;

    // Constructs and initializes the the web workers appropriately
    this.initializeWebWorkers(options);
};

AnimatedGIF.prototype = {
    'workerMethods': workerCode(),
    'initializeWebWorkers': function initializeWebWorkers(options) {
        var self = this;
        var processFrameWorkerCode = NeuQuant.toString() + '(' + workerCode.toString() + '());';
        var webWorkerObj = void 0;
        var objectUrl = void 0;
        var webWorker = void 0;
        var numWorkers = void 0;
        var x = -1;
        var workerError = '';

        numWorkers = options.numWorkers;

        while (++x < numWorkers) {
            webWorkerObj = utils.createWebWorker(processFrameWorkerCode);

            if (utils.isObject(webWorkerObj)) {
                objectUrl = webWorkerObj.objectUrl;
                webWorker = webWorkerObj.worker;

                self.workers.push({
                    worker: webWorker,
                    objectUrl: objectUrl
                });

                self.availableWorkers.push(webWorker);
            } else {
                workerError = webWorkerObj;
                utils.webWorkerError = !!webWorkerObj;
            }
        }

        this.workerError = workerError;
        this.canvas = document.createElement('canvas');
        this.canvas.width = options.gifWidth;
        this.canvas.height = options.gifHeight;
        this.ctx = this.canvas.getContext('2d');
        this.frames = [];
    },
    // Return a worker for processing a frame
    getWorker: function getWorker() {
        return this.availableWorkers.pop();
    },
    // Restores a worker to the pool
    freeWorker: function freeWorker(worker) {
        this.availableWorkers.push(worker);
    },
    byteMap: function () {
        var byteMap = [];

        for (var i = 0; i < 256; i++) {
            byteMap[i] = String.fromCharCode(i);
        }

        return byteMap;
    }(),
    bufferToString: function bufferToString(buffer) {
        var numberValues = buffer.length;
        var str = '';
        var x = -1;

        while (++x < numberValues) {
            str += this.byteMap[buffer[x]];
        }

        return str;
    },
    onFrameFinished: function onFrameFinished(progressCallback) {
        // The GIF is not written until we're done with all the frames
        // because they might not be processed in the same order
        var self = this;
        var frames = self.frames;
        var options = self.options;
        var hasExistingImages = !!(options.images || []).length;
        var allDone = frames.every(function (frame) {
            return !frame.beingProcessed && frame.done;
        });

        self.numRenderedFrames++;

        if (hasExistingImages) {
            progressCallback(self.numRenderedFrames / frames.length);
        }

        self.onRenderProgressCallback(self.numRenderedFrames * 0.75 / frames.length);

        if (allDone) {
            if (!self.generatingGIF) {
                self.generateGIF(frames, self.onRenderCompleteCallback);
            }
        } else {
            utils.requestTimeout(function () {
                self.processNextFrame();
            }, 1);
        }
    },
    processFrame: function processFrame(position) {
        var AnimatedGifContext = this;
        var options = this.options;
        var _options = this.options,
            progressCallback = _options.progressCallback,
            sampleInterval = _options.sampleInterval;

        var frames = this.frames;
        var frame = void 0;
        var worker = void 0;
        var done = function done() {
            var ev = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};

            var data = ev.data;

            // Delete original data, and free memory
            delete frame.data;

            frame.pixels = Array.prototype.slice.call(data.pixels);
            frame.palette = Array.prototype.slice.call(data.palette);
            frame.done = true;
            frame.beingProcessed = false;

            AnimatedGifContext.freeWorker(worker);

            AnimatedGifContext.onFrameFinished(progressCallback);
        };

        frame = frames[position];

        if (frame.beingProcessed || frame.done) {
            this.onFrameFinished();

            return;
        }

        frame.sampleInterval = sampleInterval;
        frame.beingProcessed = true;
        frame.gifshot = true;

        worker = this.getWorker();

        if (worker) {
            // Process the frame in a web worker
            worker.onmessage = done;
            worker.postMessage(frame);
        } else {
            // Process the frame in the current thread
            done({
                'data': AnimatedGifContext.workerMethods.run(frame)
            });
        }
    },
    startRendering: function startRendering(completeCallback) {
        this.onRenderCompleteCallback = completeCallback;

        for (var i = 0; i < this.options.numWorkers && i < this.frames.length; i++) {
            this.processFrame(i);
        }
    },
    processNextFrame: function processNextFrame() {
        var position = -1;

        for (var i = 0; i < this.frames.length; i++) {
            var frame = this.frames[i];

            if (!frame.done && !frame.beingProcessed) {
                position = i;
                break;
            }
        }

        if (position >= 0) {
            this.processFrame(position);
        }
    },
    // Takes the already processed data in frames and feeds it to a new
    // GifWriter instance in order to get the binary GIF file
    generateGIF: function generateGIF(frames, callback) {
        // TODO: Weird: using a simple JS array instead of a typed array,
        // the files are WAY smaller o_o. Patches/explanations welcome!
        var buffer = []; // new Uint8Array(width * height * frames.length * 5);
        var gifOptions = {
            loop: this.repeat
        };
        var options = this.options;
        var interval = options.interval;

        var frameDuration = options.frameDuration;
        var existingImages = options.images;
        var hasExistingImages = !!existingImages.length;
        var height = options.gifHeight;
        var width = options.gifWidth;
        var gifWriter$$1 = new gifWriter(buffer, width, height, gifOptions);
        var onRenderProgressCallback = this.onRenderProgressCallback;
        var delay = hasExistingImages ? interval * 100 : 0;
        var bufferToString = void 0;
        var gif = void 0;

        this.generatingGIF = true;

        utils.each(frames, function (iterator, frame) {
            var framePalette = frame.palette;

            onRenderProgressCallback(0.75 + 0.25 * frame.position * 1.0 / frames.length);

            for (var i = 0; i < frameDuration; i++) {
                gifWriter$$1.addFrame(0, 0, width, height, frame.pixels, {
                    palette: framePalette,
                    delay: delay
                });
            }
        });

        gifWriter$$1.end();

        onRenderProgressCallback(1.0);

        this.frames = [];

        this.generatingGIF = false;

        if (utils.isFunction(callback)) {
            bufferToString = this.bufferToString(buffer);
            gif = 'data:image/gif;base64,' + utils.btoa(bufferToString);

            callback(gif);
        }
    },
    // From GIF: 0 = loop forever, null = not looping, n > 0 = loop n times and stop
    setRepeat: function setRepeat(r) {
        this.repeat = r;
    },
    addFrame: function addFrame(element, gifshotOptions) {
        gifshotOptions = utils.isObject(gifshotOptions) ? gifshotOptions : {};

        var self = this;
        var ctx = self.ctx;
        var options = self.options;
        var width = options.gifWidth;
        var height = options.gifHeight;
        var fontSize = utils.getFontSize(gifshotOptions);
        var _gifshotOptions = gifshotOptions,
            filter = _gifshotOptions.filter,
            fontColor = _gifshotOptions.fontColor,
            fontFamily = _gifshotOptions.fontFamily,
            fontWeight = _gifshotOptions.fontWeight,
            gifHeight = _gifshotOptions.gifHeight,
            gifWidth = _gifshotOptions.gifWidth,
            text = _gifshotOptions.text,
            textAlign = _gifshotOptions.textAlign,
            textBaseline = _gifshotOptions.textBaseline;

        var textXCoordinate = gifshotOptions.textXCoordinate ? gifshotOptions.textXCoordinate : textAlign === 'left' ? 1 : textAlign === 'right' ? width : width / 2;
        var textYCoordinate = gifshotOptions.textYCoordinate ? gifshotOptions.textYCoordinate : textBaseline === 'top' ? 1 : textBaseline === 'center' ? height / 2 : height;
        var font = fontWeight + ' ' + fontSize + ' ' + fontFamily;
        var imageData = void 0;

        try {
            ctx.filter = filter;

            ctx.drawImage(element, 0, 0, width, height);

            if (text) {
                ctx.font = font;
                ctx.fillStyle = fontColor;
                ctx.textAlign = textAlign;
                ctx.textBaseline = textBaseline;
                ctx.fillText(text, textXCoordinate, textYCoordinate);
            }

            imageData = ctx.getImageData(0, 0, width, height);

            self.addFrameImageData(imageData);
        } catch (e) {
            return '' + e;
        }
    },
    addFrameImageData: function addFrameImageData() {
        var imageData = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};

        var frames = this.frames;
        var imageDataArray = imageData.data;

        this.frames.push({
            'data': imageDataArray,
            'width': imageData.width,
            'height': imageData.height,
            'palette': null,
            'dithering': null,
            'done': false,
            'beingProcessed': false,
            'position': frames.length
        });
    },
    onRenderProgress: function onRenderProgress(callback) {
        this.onRenderProgressCallback = callback;
    },
    isRendering: function isRendering() {
        return this.generatingGIF;
    },
    getBase64GIF: function getBase64GIF(completeCallback) {
        var self = this;
        var onRenderComplete = function onRenderComplete(gif) {
            self.destroyWorkers();

            utils.requestTimeout(function () {
                completeCallback(gif);
            }, 0);
        };

        self.startRendering(onRenderComplete);
    },
    destroyWorkers: function destroyWorkers() {
        if (this.workerError) {
            return;
        }

        var workers = this.workers;

        // Explicitly ask web workers to die so they are explicitly GC'ed
        utils.each(workers, function (iterator, workerObj) {
            var worker = workerObj.worker;
            var objectUrl = workerObj.objectUrl;

            worker.terminate();
            utils.URL.revokeObjectURL(objectUrl);
        });
    }
};

/*
  getBase64GIF.js
  ===============
*/

/* Copyright  2017 Yahoo Inc.
 * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms.
*/

function getBase64GIF(animatedGifInstance, callback) {
    // This is asynchronous, rendered with WebWorkers
    animatedGifInstance.getBase64GIF(function (image) {
        callback({
            error: false,
            errorCode: '',
            errorMsg: '',
            image: image
        });
    });
}

/*
  existingImages.js
  =================
*/

/* Copyright  2017 Yahoo Inc.
 * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms.
*/

function existingImages() {
    var obj = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};

    var self = this;
    var callback = obj.callback,
        images = obj.images,
        options = obj.options;

    var imagesLength = obj.imagesLength;
    var skipObj = {
        'getUserMedia': true,
        'window.URL': true
    };
    var errorObj = error.validate(skipObj);
    var loadedImages = [];
    var loadedImagesLength = 0;
    var tempImage = void 0;
    var ag = void 0;

    if (errorObj.error) {
        return callback(errorObj);
    }

    // change workerPath to point to where Animated_GIF.worker.js is
    ag = new AnimatedGIF(options);

    utils.each(images, function (index, image) {
        var currentImage = image;

        if (image.src) {
            currentImage = currentImage.src;
        }
        if (utils.isElement(currentImage)) {
            if (options.crossOrigin) {
                currentImage.crossOrigin = options.crossOrigin;
            }

            loadedImages[index] = currentImage;
            loadedImagesLength += 1;

            if (loadedImagesLength === imagesLength) {
                addLoadedImagesToGif();
            }
        } else if (utils.isString(currentImage)) {
            tempImage = new Image();

            if (options.crossOrigin) {
                tempImage.crossOrigin = options.crossOrigin;
            }

            (function (tempImage) {
                if (image.text) {
                    tempImage.text = image.text;
                }

                tempImage.onerror = function (e) {
                    var obj = void 0;

                    --imagesLength; // skips over images that error out

                    if (imagesLength === 0) {
                        obj = {};
                        obj.error = 'None of the requested images was capable of being retrieved';

                        return callback(obj);
                    }
                };

                tempImage.onload = function (e) {
                    if (image.text) {
                        loadedImages[index] = {
                            img: tempImage,
                            text: tempImage.text
                        };
                    } else {
                        loadedImages[index] = tempImage;
                    }

                    loadedImagesLength += 1;

                    if (loadedImagesLength === imagesLength) {
                        addLoadedImagesToGif();
                    }

                    utils.removeElement(tempImage);
                };

                tempImage.src = currentImage;
            })(tempImage);

            utils.setCSSAttr(tempImage, {
                position: 'fixed',
                opacity: '0'
            });

            document.body.appendChild(tempImage);
        }
    });

    function addLoadedImagesToGif() {
        utils.each(loadedImages, function (index, loadedImage) {
            if (loadedImage) {
                if (loadedImage.text) {
                    ag.addFrame(loadedImage.img, options, loadedImage.text);
                } else {
                    ag.addFrame(loadedImage, options);
                }
            }
        });

        getBase64GIF(ag, callback);
    }
}

/*
  screenShot.js
  =============
*/

/* Copyright  2017 Yahoo Inc.
 * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms.
*/

// Dependencies
// Helpers
var noop$3 = function noop() {};

var screenShot = {
    getGIF: function getGIF() {
        var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
        var callback = arguments[1];

        callback = utils.isFunction(callback) ? callback : noop$3;

        var canvas = document.createElement('canvas');
        var context = void 0;
        var existingImages = options.images;
        var hasExistingImages = !!existingImages.length;
        var cameraStream = options.cameraStream,
            crop = options.crop,
            filter = options.filter,
            fontColor = options.fontColor,
            fontFamily = options.fontFamily,
            fontWeight = options.fontWeight,
            keepCameraOn = options.keepCameraOn,
            numWorkers = options.numWorkers,
            progressCallback = options.progressCallback,
            saveRenderingContexts = options.saveRenderingContexts,
            savedRenderingContexts = options.savedRenderingContexts,
            text = options.text,
            textAlign = options.textAlign,
            textBaseline = options.textBaseline,
            videoElement = options.videoElement,
            videoHeight = options.videoHeight,
            videoWidth = options.videoWidth,
            webcamVideoElement = options.webcamVideoElement;

        var gifWidth = Number(options.gifWidth);
        var gifHeight = Number(options.gifHeight);
        var interval = Number(options.interval);
        var sampleInterval = Number(options.sampleInterval);
        var waitBetweenFrames = hasExistingImages ? 0 : interval * 1000;
        var renderingContextsToSave = [];
        var numFrames = savedRenderingContexts.length ? savedRenderingContexts.length : options.numFrames;
        var pendingFrames = numFrames;
        var ag = new AnimatedGIF(options);
        var fontSize = utils.getFontSize(options);
        var textXCoordinate = options.textXCoordinate ? options.textXCoordinate : textAlign === 'left' ? 1 : textAlign === 'right' ? gifWidth : gifWidth / 2;
        var textYCoordinate = options.textYCoordinate ? options.textYCoordinate : textBaseline === 'top' ? 1 : textBaseline === 'center' ? gifHeight / 2 : gifHeight;
        var font = fontWeight + ' ' + fontSize + ' ' + fontFamily;
        var sourceX = crop ? Math.floor(crop.scaledWidth / 2) : 0;
        var sourceWidth = crop ? videoWidth - crop.scaledWidth : 0;
        var sourceY = crop ? Math.floor(crop.scaledHeight / 2) : 0;
        var sourceHeight = crop ? videoHeight - crop.scaledHeight : 0;
        var captureFrames = function captureSingleFrame() {
            var framesLeft = pendingFrames - 1;

            if (savedRenderingContexts.length) {
                context.putImageData(savedRenderingContexts[numFrames - pendingFrames], 0, 0);

                finishCapture();
            } else {
                drawVideo();
            }

            function drawVideo() {
                try {
                    // Makes sure the canvas video heights/widths are in bounds
                    if (sourceWidth > videoWidth) {
                        sourceWidth = videoWidth;
                    }

                    if (sourceHeight > videoHeight) {
                        sourceHeight = videoHeight;
                    }

                    if (sourceX < 0) {
                        sourceX = 0;
                    }

                    if (sourceY < 0) {
                        sourceY = 0;
                    }

                    context.filter = filter;

                    context.drawImage(videoElement, sourceX, sourceY, sourceWidth, sourceHeight, 0, 0, gifWidth, gifHeight);

                    finishCapture();
                } catch (e) {
                    // There is a Firefox bug that sometimes throws NS_ERROR_NOT_AVAILABLE and
                    // and IndexSizeError errors when drawing a video element to the canvas
                    if (e.name === 'NS_ERROR_NOT_AVAILABLE') {
                        // Wait 100ms before trying again
                        utils.requestTimeout(drawVideo, 100);
                    } else {
                        throw e;
                    }
                }
            }

            function finishCapture() {
                var imageData = void 0;

                if (saveRenderingContexts) {
                    renderingContextsToSave.push(context.getImageData(0, 0, gifWidth, gifHeight));
                }

                // If there is text to display, make sure to display it on the canvas after the image is drawn
                if (text) {
                    context.font = font;
                    context.fillStyle = fontColor;
                    context.textAlign = textAlign;
                    context.textBaseline = textBaseline;
                    context.fillText(text, textXCoordinate, textYCoordinate);
                }

                imageData = context.getImageData(0, 0, gifWidth, gifHeight);

                ag.addFrameImageData(imageData);

                pendingFrames = framesLeft;

                // Call back with an r value indicating how far along we are in capture
                progressCallback((numFrames - pendingFrames) / numFrames);

                if (framesLeft > 0) {
                    // test
                    utils.requestTimeout(captureSingleFrame, waitBetweenFrames);
                }

                if (!pendingFrames) {
                    ag.getBase64GIF(function (image) {
                        callback({
                            'error': false,
                            'errorCode': '',
                            'errorMsg': '',
                            'image': image,
                            'cameraStream': cameraStream,
                            'videoElement': videoElement,
                            'webcamVideoElement': webcamVideoElement,
                            'savedRenderingContexts': renderingContextsToSave,
                            'keepCameraOn': keepCameraOn
                        });
                    });
                }
            }
        };

        numFrames = numFrames !== undefined ? numFrames : 10;
        interval = interval !== undefined ? interval : 0.1; // In seconds

        canvas.width = gifWidth;
        canvas.height = gifHeight;
        context = canvas.getContext('2d');

        (function capture() {
            if (!savedRenderingContexts.length && videoElement.currentTime === 0) {
                utils.requestTimeout(capture, 100);

                return;
            }

            captureFrames();
        })();
    },
    getCropDimensions: function getCropDimensions() {
        var obj = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};

        var width = obj.videoWidth;
        var height = obj.videoHeight;
        var gifWidth = obj.gifWidth;
        var gifHeight = obj.gifHeight;
        var result = {
            width: 0,
            height: 0,
            scaledWidth: 0,
            scaledHeight: 0
        };

        if (width > height) {
            result.width = Math.round(width * (gifHeight / height)) - gifWidth;
            result.scaledWidth = Math.round(result.width * (height / gifHeight));
        } else {
            result.height = Math.round(height * (gifWidth / width)) - gifHeight;
            result.scaledHeight = Math.round(result.height * (width / gifWidth));
        }

        return result;
    }
};

/*
  videoStream.js
  ==============
*/

/* Copyright  2017 Yahoo Inc.
 * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms.
*/

// Dependencies
var videoStream = {
    loadedData: false,
    defaultVideoDimensions: {
        width: 640,
        height: 480
    },
    findVideoSize: function findVideoSizeMethod(obj) {
        findVideoSizeMethod.attempts = findVideoSizeMethod.attempts || 0;

        var cameraStream = obj.cameraStream,
            completedCallback = obj.completedCallback,
            videoElement = obj.videoElement;


        if (!videoElement) {
            return;
        }

        if (videoElement.videoWidth > 0 && videoElement.videoHeight > 0) {
            videoElement.removeEventListener('loadeddata', videoStream.findVideoSize);

            completedCallback({
                videoElement: videoElement,
                cameraStream: cameraStream,
                videoWidth: videoElement.videoWidth,
                videoHeight: videoElement.videoHeight
            });
        } else {
            if (findVideoSizeMethod.attempts < 10) {
                findVideoSizeMethod.attempts += 1;

                utils.requestTimeout(function () {
                    videoStream.findVideoSize(obj);
                }, 400);
            } else {
                completedCallback({
                    videoElement: videoElement,
                    cameraStream: cameraStream,
                    videoWidth: videoStream.defaultVideoDimensions.width,
                    videoHeight: videoStream.defaultVideoDimensions.height
                });
            }
        }
    },
    onStreamingTimeout: function onStreamingTimeout(callback) {
        if (utils.isFunction(callback)) {
            callback({
                error: true,
                errorCode: 'getUserMedia',
                errorMsg: 'There was an issue with the getUserMedia API - Timed out while trying to start streaming',
                image: null,
                cameraStream: {}
            });
        }
    },
    stream: function stream(obj) {
        var existingVideo = utils.isArray(obj.existingVideo) ? obj.existingVideo[0] : obj.existingVideo;
        var cameraStream = obj.cameraStream,
            completedCallback = obj.completedCallback,
            streamedCallback = obj.streamedCallback,
            videoElement = obj.videoElement;


        if (utils.isFunction(streamedCallback)) {
            streamedCallback();
        }

        if (existingVideo) {
            if (utils.isString(existingVideo)) {
                videoElement.src = existingVideo;
                videoElement.innerHTML = '<source src="' + existingVideo + '" type="video/' + utils.getExtension(existingVideo) + '" />';
            } else if (existingVideo instanceof Blob) {
                videoElement.src = utils.URL.createObjectURL(existingVideo);
                videoElement.innerHTML = '<source src="' + existingVideo + '" type="' + existingVideo.type + '" />';
            }
        } else if (videoElement.mozSrcObject) {
            videoElement.mozSrcObject = cameraStream;
        } else if (utils.URL) {
            videoElement.src = utils.URL.createObjectURL(cameraStream);
        }

        videoElement.play();

        utils.requestTimeout(function checkLoadedData() {
            checkLoadedData.count = checkLoadedData.count || 0;

            if (videoStream.loadedData === true) {
                videoStream.findVideoSize({
                    videoElement: videoElement,
                    cameraStream: cameraStream,
                    completedCallback: completedCallback
                });

                videoStream.loadedData = false;
            } else {
                checkLoadedData.count += 1;

                if (checkLoadedData.count > 10) {
                    videoStream.findVideoSize({
                        videoElement: videoElement,
                        cameraStream: cameraStream,
                        completedCallback: completedCallback
                    });
                } else {
                    checkLoadedData();
                }
            }
        }, 0);
    },
    startStreaming: function startStreaming(obj) {
        var errorCallback = utils.isFunction(obj.error) ? obj.error : utils.noop;
        var streamedCallback = utils.isFunction(obj.streamed) ? obj.streamed : utils.noop;
        var completedCallback = utils.isFunction(obj.completed) ? obj.completed : utils.noop;
        var crossOrigin = obj.crossOrigin,
            existingVideo = obj.existingVideo,
            lastCameraStream = obj.lastCameraStream,
            options = obj.options,
            webcamVideoElement = obj.webcamVideoElement;

        var videoElement = utils.isElement(existingVideo) ? existingVideo : webcamVideoElement ? webcamVideoElement : document.createElement('video');
        var cameraStream = void 0;

        if (crossOrigin) {
            videoElement.crossOrigin = options.crossOrigin;
        }

        videoElement.autoplay = true;
        videoElement.loop = true;
        videoElement.muted = true;
        videoElement.addEventListener('loadeddata', function (event) {
            videoStream.loadedData = true;
        });

        if (existingVideo) {
            videoStream.stream({
                videoElement: videoElement,
                existingVideo: existingVideo,
                completedCallback: completedCallback
            });
        } else if (lastCameraStream) {
            videoStream.stream({
                videoElement: videoElement,
                cameraStream: lastCameraStream,
                streamedCallback: streamedCallback,
                completedCallback: completedCallback
            });
        } else {
            utils.getUserMedia({
                video: true
            }, function (stream) {
                videoStream.stream({
                    videoElement: videoElement,
                    cameraStream: stream,
                    streamedCallback: streamedCallback,
                    completedCallback: completedCallback
                });
            }, errorCallback);
        }
    },
    startVideoStreaming: function startVideoStreaming(callback) {
        var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};

        var timeoutLength = options.timeout !== undefined ? options.timeout : 0;
        var originalCallback = options.callback;
        var webcamVideoElement = options.webcamVideoElement;
        var noGetUserMediaSupportTimeout = void 0;

        // Some browsers apparently have support for video streaming because of the
        // presence of the getUserMedia function, but then do not answer our
        // calls for streaming.
        // So we'll set up this timeout and if nothing happens after a while, we'll
        // conclude that there's no actual getUserMedia support.
        if (timeoutLength > 0) {
            noGetUserMediaSupportTimeout = utils.requestTimeout(function () {
                videoStream.onStreamingTimeout(originalCallback);
            }, 10000);
        }

        videoStream.startStreaming({
            error: function error() {
                originalCallback({
                    error: true,
                    errorCode: 'getUserMedia',
                    errorMsg: 'There was an issue with the getUserMedia API - the user probably denied permission',
                    image: null,
                    cameraStream: {}
                });
            },
            streamed: function streamed() {
                // The streaming started somehow, so we can assume there is getUserMedia support
                clearTimeout(noGetUserMediaSupportTimeout);
            },
            completed: function completed() {
                var obj = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
                var cameraStream = obj.cameraStream,
                    videoElement = obj.videoElement,
                    videoHeight = obj.videoHeight,
                    videoWidth = obj.videoWidth;


                callback({
                    cameraStream: cameraStream,
                    videoElement: videoElement,
                    videoHeight: videoHeight,
                    videoWidth: videoWidth
                });
            },
            lastCameraStream: options.lastCameraStream,
            webcamVideoElement: webcamVideoElement,
            crossOrigin: options.crossOrigin,
            options: options
        });
    },
    stopVideoStreaming: function stopVideoStreaming(obj) {
        obj = utils.isObject(obj) ? obj : {};

        var _obj = obj,
            keepCameraOn = _obj.keepCameraOn,
            videoElement = _obj.videoElement,
            webcamVideoElement = _obj.webcamVideoElement;

        var cameraStream = obj.cameraStream || {};
        var cameraStreamTracks = cameraStream.getTracks ? cameraStream.getTracks() || [] : [];
        var hasCameraStreamTracks = !!cameraStreamTracks.length;
        var firstCameraStreamTrack = cameraStreamTracks[0];

        if (!keepCameraOn && hasCameraStreamTracks) {
            if (utils.isFunction(firstCameraStreamTrack.stop)) {
                // Stops the camera stream
                firstCameraStreamTrack.stop();
            }
        }

        if (utils.isElement(videoElement) && !webcamVideoElement) {
            // Pauses the video, revokes the object URL (freeing up memory), and remove the video element
            videoElement.pause();

            // Destroys the object url
            if (utils.isFunction(utils.URL.revokeObjectURL) && !utils.webWorkerError) {
                if (videoElement.src) {
                    utils.URL.revokeObjectURL(videoElement.src);
                }
            }

            // Removes the video element from the DOM
            utils.removeElement(videoElement);
        }
    }
};

/*
  stopVideoStreaming.js
  =====================
*/

/* Copyright  2017 Yahoo Inc.
 * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms.
*/

function stopVideoStreaming(options) {
  options = utils.isObject(options) ? options : {};

  videoStream.stopVideoStreaming(options);
}

/*
  createAndGetGIF.js
  ==================
*/

/* Copyright  2017 Yahoo Inc.
 * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms.
*/

// Dependencies
function createAndGetGIF(obj, callback) {
    var options = obj.options || {};

    var images = options.images,
        video = options.video;

    var gifWidth = Number(options.gifWidth);
    var gifHeight = Number(options.gifHeight);
    var numFrames = Number(options.numFrames);
    var cameraStream = obj.cameraStream,
        videoElement = obj.videoElement,
        videoWidth = obj.videoWidth,
        videoHeight = obj.videoHeight;

    var cropDimensions = screenShot.getCropDimensions({
        videoWidth: videoWidth,
        videoHeight: videoHeight,
        gifHeight: gifHeight,
        gifWidth: gifWidth
    });
    var completeCallback = callback;

    options.crop = cropDimensions;
    options.videoElement = videoElement;
    options.videoWidth = videoWidth;
    options.videoHeight = videoHeight;
    options.cameraStream = cameraStream;

    if (!utils.isElement(videoElement)) {
        return;
    }

    videoElement.width = gifWidth + cropDimensions.width;
    videoElement.height = gifHeight + cropDimensions.height;

    if (!options.webcamVideoElement) {
        utils.setCSSAttr(videoElement, {
            position: 'fixed',
            opacity: '0'
        });

        document.body.appendChild(videoElement);
    }

    // Firefox doesn't seem to obey autoplay if the element is not in the DOM when the content
    // is loaded, so we must manually trigger play after adding it, or the video will be frozen
    videoElement.play();

    screenShot.getGIF(options, function (obj) {
        if ((!images || !images.length) && (!video || !video.length)) {
            stopVideoStreaming(obj);
        }

        completeCallback(obj);
    });
}

/*
  existingVideo.js
  ================
*/

/* Copyright  2017 Yahoo Inc.
 * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms.
*/

// Dependencies
function existingVideo() {
    var obj = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
    var callback = obj.callback,
        existingVideo = obj.existingVideo,
        options = obj.options;

    var skipObj = {
        getUserMedia: true,
        'window.URL': true
    };
    var errorObj = error.validate(skipObj);
    var loadedImages = 0;
    var videoType = void 0;
    var videoSrc = void 0;
    var tempImage = void 0;
    var ag = void 0;

    if (errorObj.error) {
        return callback(errorObj);
    }

    if (utils.isElement(existingVideo) && existingVideo.src) {
        videoSrc = existingVideo.src;
        videoType = utils.getExtension(videoSrc);

        if (!utils.isSupported.videoCodecs[videoType]) {
            return callback(error.messages.videoCodecs);
        }
    } else if (utils.isArray(existingVideo)) {
        utils.each(existingVideo, function (iterator, videoSrc) {
            if (videoSrc instanceof Blob) {
                videoType = videoSrc.type.substr(videoSrc.type.lastIndexOf('/') + 1, videoSrc.length);
            } else {
                videoType = videoSrc.substr(videoSrc.lastIndexOf('.') + 1, videoSrc.length);
            }

            if (utils.isSupported.videoCodecs[videoType]) {
                existingVideo = videoSrc;

                return false;
            }
        });
    }

    videoStream.startStreaming({
        completed: function completed(obj) {
            obj.options = options || {};

            createAndGetGIF(obj, callback);
        },
        existingVideo: existingVideo,
        crossOrigin: options.crossOrigin,
        options: options
    });
}

/*
  existingWebcam.js
  =================
*/

/* Copyright  2017 Yahoo Inc.
 * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms.
*/

// Dependencies
function existingWebcam() {
    var obj = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
    var callback = obj.callback,
        lastCameraStream = obj.lastCameraStream,
        options = obj.options,
        webcamVideoElement = obj.webcamVideoElement;


    if (!isWebCamGIFSupported()) {
        return callback(error.validate());
    }

    if (options.savedRenderingContexts.length) {
        screenShot.getGIF(options, function (obj) {
            callback(obj);
        });

        return;
    }

    videoStream.startVideoStreaming(function () {
        var obj = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};

        obj.options = options || {};

        createAndGetGIF(obj, callback);
    }, {
        lastCameraStream: lastCameraStream,
        callback: callback,
        webcamVideoElement: webcamVideoElement,
        crossOrigin: options.crossOrigin
    });
}

/*
  createGIF.js
  ============
*/

/* Copyright  2017 Yahoo Inc.
 * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms.
*/

// Dependencies
function createGIF(userOptions, callback) {
  callback = utils.isFunction(userOptions) ? userOptions : callback;
  userOptions = utils.isObject(userOptions) ? userOptions : {};

  if (!utils.isFunction(callback)) {
    return;
  }

  var options = utils.mergeOptions(defaultOptions, userOptions) || {};
  var lastCameraStream = userOptions.cameraStream;
  var images = options.images;
  var imagesLength = images ? images.length : 0;
  var video = options.video;
  var webcamVideoElement = options.webcamVideoElement;

  options = utils.mergeOptions(options, {
    'gifWidth': Math.floor(options.gifWidth),
    'gifHeight': Math.floor(options.gifHeight)
  });

  // If the user would like to create a GIF from an existing image(s)
  if (imagesLength) {
    existingImages({
      'images': images,
      'imagesLength': imagesLength,
      'callback': callback,
      'options': options
    });
  } else if (video) {
    // If the user would like to create a GIF from an existing HTML5 video
    existingVideo({
      'existingVideo': video,
      callback: callback,
      options: options
    });
  } else {
    // If the user would like to create a GIF from a webcam stream
    existingWebcam({
      lastCameraStream: lastCameraStream,
      callback: callback,
      webcamVideoElement: webcamVideoElement,
      options: options
    });
  }
}

/*
  takeSnapShot.js
  ===============
*/

/* Copyright  2017 Yahoo Inc.
 * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms.
*/

function takeSnapShot(userOptions, callback) {
    callback = utils.isFunction(userOptions) ? userOptions : callback;
    userOptions = utils.isObject(userOptions) ? userOptions : {};

    if (!utils.isFunction(callback)) {
        return;
    }

    var mergedOptions = utils.mergeOptions(defaultOptions, userOptions);
    var options = utils.mergeOptions(mergedOptions, {
        'interval': .1,
        'numFrames': 1,
        'gifWidth': Math.floor(mergedOptions.gifWidth),
        'gifHeight': Math.floor(mergedOptions.gifHeight)
    });

    createGIF(options, callback);
}

/*
  API.js
  ======
*/

/* Copyright  2017 Yahoo Inc.
 * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms.
*/

// Dependencies
var API = {
  'utils': utils$2,
  'error': error$2,
  'defaultOptions': defaultOptions$2,
  'createGIF': createGIF,
  'takeSnapShot': takeSnapShot,
  'stopVideoStreaming': stopVideoStreaming,
  'isSupported': isSupported,
  'isWebCamGIFSupported': isWebCamGIFSupported,
  'isExistingVideoGIFSupported': isExistingVideoGIFSupported,
  'isExistingImagesGIFSupported': isSupported$1,
  'VERSION': '0.4.4'
};

/*
  index.js
  ========
*/

/* Copyright  2017 Yahoo Inc.
 * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms.
 */

// Universal Module Definition (UMD) to support AMD, CommonJS/Node.js, and plain browser loading
if (typeof define === 'function' && define.amd) {
    define([], function () {
        return API;
    });
} else if (typeof exports !== 'undefined') {
    module.exports = API;
} else {
    window.gifshot = API;
}
}(typeof window !== "undefined" ? window : {}, typeof document !== "undefined" ? document : { createElement: function() {} }, typeof window !== "undefined" ? window.navigator : {}));
